package com.iocup.keybastion.sso.core;


import com.ejlchina.okhttps.HTTP;
import com.ejlchina.okhttps.HttpResult;
import com.ejlchina.okhttps.OkHttps;
import com.iocup.keybastion.authentication.userdetails.UserDetails;
import com.iocup.keybastion.authentication.userdetails.service.UserDetailService;
import com.iocup.keybastion.context.SecurityContextHolder;
import com.iocup.keybastion.context.WebContextHolder;
import com.iocup.keybastion.core.profile.CommonUserProfile;
import com.iocup.keybastion.core.profile.UserProfile;
import com.iocup.keybastion.core.store.CacheStore;
import com.iocup.keybastion.exception.AuthenticationException;
import com.iocup.keybastion.sso.ClientSsoAuthLogic;
import com.iocup.keybastion.sso.common.RequestParam;
import com.iocup.keybastion.sso.common.SsoConstant;
import com.iocup.keybastion.sso.common.SsoRouterNotFoundException;
import com.iocup.keybastion.sso.common.SsoUtils;
import com.iocup.keybastion.sso.config.SsoApiProperties;
import com.iocup.keybastion.sso.config.SsoProperties;
import com.iocup.keybastion.utils.AuthUtils;
import com.pine.sunflower.core.model.HttpStatus;
import com.pine.sunflower.core.model.Result;
import com.pine.sunflower.core.utils.JsonUtils;
import com.pine.sunflower.core.utils.Strings;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;
import java.util.Optional;

/**
 * @author xyjxust
 * @create 2023/1/18 17:45
 **/
@Slf4j
public class ClientSsoAuthLogicImpl implements ClientSsoAuthLogic {

    private SsoProperties ssoProperties;

    private SsoApiProperties ssoApiProperties = new SsoApiProperties();


    private SsoAuthFunction ssoAuthFunction;


    private CacheStore cacheStore;

    private UserDetailService userDetailService;

    @Setter
    @Getter
    private HTTP http;

    public ClientSsoAuthLogicImpl(SsoProperties ssoProperties,
                                  SsoApiProperties ssoApiProperties,
                                  SsoAuthFunction ssoAuthFunction, CacheStore cacheStore, UserDetailService userDetailService) {
        this.ssoProperties = ssoProperties;
        this.ssoApiProperties = ssoApiProperties;
        this.ssoAuthFunction = ssoAuthFunction;
        this.cacheStore = cacheStore;
        this.userDetailService = userDetailService;
    }

    @Override
    public Object handle() {
        WebContextHolder webContextHolder = SecurityContextHolder.getContext().getWebContextHolder();
        String uri = webContextHolder.getRequestURL();
        String method = webContextHolder.getRequestMethod();
        if (uri.equals(ssoApiProperties.ssoLogin) && "GET".equalsIgnoreCase(method)) {
            return clientSsoLogin(webContextHolder);
        }
        if (uri.equals(ssoApiProperties.ssoLogin) && "POST".equalsIgnoreCase(method)) {
            return Result.success(AuthUtils.getToken());
        }
        if (uri.equals(ssoApiProperties.ssoCodeLogin) && "POST".equalsIgnoreCase(method)) {
            return loginByCode(webContextHolder);
        }
        if (uri.equals(ssoApiProperties.ssoCallback) && "GET".equalsIgnoreCase(method)) {
            return clientSsoCallback(webContextHolder);
        }
        if (uri.equals(ssoApiProperties.ssoLogout) && "POST".equalsIgnoreCase(method)) {
            return clientSsoLogout(webContextHolder);
        }
        if (uri.equals(ssoApiProperties.ssoUserinfo) && "GET".equalsIgnoreCase(method)) {
            UserProfile userProfile  =   AuthUtils.getUserInfo();
            if(userProfile==null){
                throw new AuthenticationException("认证凭证为空");
            }
            return Result.success(userProfile);
        }
        throw new SsoRouterNotFoundException(HttpStatus.METHOD_NOT_ALLOWED.getDescribe());
    }

    private Object clientSsoLogin(WebContextHolder webContextHolder) {
        if (!AuthUtils.isLogin()) {
            ssoAuthFunction.handle(SecurityContextHolder.getContext().getWebContextHolder());
            return null;
        }
        WebContextHolder contextHolder = SecurityContextHolder.getContext().getWebContextHolder();
        Optional<String> optionalRedirect = contextHolder.getRequestParameter(SsoConstant.redirect);
        //如果是访问单点登录的登录接口，说明是前后端分离状态，那么跳转页面需要获取传入的redirect地址的
        if (contextHolder.getRequestURL().equals(this.ssoProperties.getClientLoginUrl()) &&
                optionalRedirect.isPresent()) {
            contextHolder.responseRedirect(optionalRedirect.get());
            return null;
        }
        return Result.success(AuthUtils.getToken());
    }

    private Object loginByCode(WebContextHolder webContextHolder) {
        log.debug("=== CODE LOGIN ===");
        Map<String, String> params = webContextHolder.getRequestBody();
        if (params == null || params.size() == 0) {
            throw new IllegalArgumentException("未传输认证参数");
        }
        String state = Optional.ofNullable(params.get(SsoConstant.state)).orElseThrow(() -> new IllegalArgumentException("state不能为空"));
        String code = Optional.ofNullable(params.get(SsoConstant.code)).orElseThrow(() -> new IllegalArgumentException("code不能为空"));
        RequestParam requestParam = this.cacheStore.getValue(SsoUtils.buildStateKey(state));
        if (requestParam == null) {
            throw new AuthenticationException("请求超时或state为非法字符");
        }
        try {
            UserProfile userProfile = getUserProfile(code, requestParam.getVerifier());
            if (userProfile == null) {
                throw new AuthenticationException("ticket错误");
            }
            if (ssoProperties.isClientGetPermission()) {
                UserDetails userDetails = userDetailService.loadUserByUsername(userProfile.getLoginName());
                if (userDetails == null) {
                    throw new AuthenticationException("客户端未查询到用户信息");
                }
                userProfile.setPermissions(userDetails.getResources());
                userProfile.setRoles(userDetails.getRoles());
            }
            AuthUtils.saveUserProfile(userProfile);
        } finally {
            this.cacheStore.removeValue(SsoUtils.buildStateKey(state));
        }
        return Result.success(AuthUtils.getToken());
    }

    private Object clientSsoLogout(WebContextHolder webContextHolder) {
        Map<String, String> params = webContextHolder.getRequestBody();
        if (params != null && params.size() > 0 && params.containsKey(SsoConstant.loginId)) {
            String loginId = params.get(SsoConstant.loginId);
            Optional<String> optionalClient = webContextHolder.getRequestHeader("Authorization");
            if (!optionalClient.isPresent()) {
                throw new AuthenticationException("客户端信息不存在");
            }
            //检查客户端的id和密钥信息
            String[] clientInfo = SsoUtils.decodeClientAuthenticationHeader(optionalClient.get());
            if (clientInfo == null) {
                throw new AuthenticationException("客户端信息不存在");
            }
            checkClientInfo(clientInfo[0], clientInfo[1]);
            AuthUtils.logout(loginId, "", "");
            return null;

        }


        if (!AuthUtils.isLogin()) {
            throw new AuthenticationException("登录凭证错误");
        }
        Result result = httpServerLogout(this.ssoProperties.getServeLogoutUrl());
        if (result.isSuccess()) {
            AuthUtils.logout();
        }
        return null;
    }

    private void checkClientInfo(String clientId, String clientSecret) {
        if (!ssoProperties.getClientSecret().equals(clientSecret) && !ssoProperties.getClientId().equals(clientId)) {
            throw new AuthenticationException(Strings.format("客户端[{}]信息错误", clientId));
        }
    }

    private Object clientSsoCallback(WebContextHolder webContextHolder) {
        Optional<String> optionalState = webContextHolder.getRequestParameter(SsoConstant.state);
        log.debug("=== CALLBACK ===");
        if (!optionalState.isPresent()) {
            throw new AuthenticationException("未找到state");
        }
        Optional<String> ticket = webContextHolder.getRequestParameter(SsoConstant.ticket);
        if (!ticket.isPresent()) {
            throw new AuthenticationException("未查询到ticket");
        }
        RequestParam requestParam = this.cacheStore.getValue(SsoUtils.buildStateKey(optionalState.get()));
        if (requestParam == null) {
            throw new AuthenticationException("请求超时或state为非法字符");
        }
        //如果是前后端分离，需要返回ticket到前端，由前端根据ticket进行登录获取token
        if (ssoProperties.isFrontSep()) {
            webContextHolder.responseRedirect(buildRequestUrl(requestParam.getRequestUrl(), ticket.get(), optionalState.get()));
            return null;
        }
        try {
            UserProfile userProfile = getUserProfile(ticket.get(), requestParam.getVerifier());

            if (userProfile == null) {
                throw new AuthenticationException("ticket错误");
            }
            if (ssoProperties.isClientGetPermission()) {
                UserDetails userDetails = userDetailService.loadUserByUsername(userProfile.getLoginName());
                if (userDetails == null) {
                    throw new AuthenticationException("客户端未查询到用户信息");
                }
                userProfile.setPermissions(userDetails.getResources());
                userProfile.setRoles(userDetails.getRoles());
            }
            AuthUtils.saveUserProfile(userProfile);

            webContextHolder.responseRedirect(requestParam.getRequestUrl());
        } finally {
            optionalState.ifPresent(s -> this.cacheStore.removeValue(SsoUtils.buildStateKey(s)));
        }
        return null;

    }

    private Result<Boolean> httpServerLogout(String url) {
        HttpResult httpResult = http.sync(url)
                .addHeader("Content-Type", "application/json")
                .bodyType(OkHttps.JSON)
                .addBodyPara(SsoConstant.loginId, AuthUtils.getLoginName())
                .addHeader("Authorization", SsoUtils.getAuthorizationHeader(this.ssoProperties.getClientId(), this.ssoProperties.getClientSecret()))
                .post();
        if (!httpResult.isSuccessful()) {
            throw new AuthenticationException(String.format("登出认证服务器失败，原因：%s", httpResult.getBody().toString()));
        }
        Result<String> result = JsonUtils.toObject(httpResult.getBody().toString(), Result.class);
        if (!result.isSuccess()) {
            throw new AuthenticationException(String.format("登出认证服务器失败，原因：%s", result.getMessage()));
        }
        return Result.success();
    }

    private UserProfile getUserProfile(String ticket, String verifier) {
        HttpResult result = http.sync(ssoProperties.getCheckTicketUrl())
                .addHeader("Content-Type", "application/json")
                .bodyType(OkHttps.JSON)
                .addBodyPara(SsoConstant.ticket,ticket)
                .addBodyPara(SsoConstant.code_verifier,verifier)
                .addHeader("Authorization", SsoUtils.getAuthorizationHeader(this.ssoProperties.getClientId(), this.ssoProperties.getClientSecret()))
                .post();
        if (!result.isSuccessful()) {
            throw new AuthenticationException(String.format("认证服务器校验ticket失败，原因：%s", result.getBody().toString()));
        }
        Result<String> commonUserProfileResult = JsonUtils.toObject(result.getBody().toString(), Result.class);
        if (!commonUserProfileResult.isSuccess()) {
            throw new AuthenticationException(String.format("认证服务器校验ticket失败，原因：%s", commonUserProfileResult.getMessage()));
        }
        return JsonUtils.toObject(JsonUtils.toString(commonUserProfileResult.getData()), CommonUserProfile.class);
    }

    private String buildRequestUrl(String requestUrl, String code, String state) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(requestUrl);
        if (StringUtils.containsIgnoreCase(requestUrl, "?")) {
            stringBuilder.append("&");
        } else {
            stringBuilder.append("?");
        }
        stringBuilder.append(SsoConstant.code);
        stringBuilder.append("=");
        stringBuilder.append(code);
        stringBuilder.append("&");
        stringBuilder.append(SsoConstant.state);
        stringBuilder.append("=");
        stringBuilder.append(state);
        return stringBuilder.toString();
    }

}
